package com.basic.minio.utils;

import com.basic.common.utils.DateUtils;
import com.basic.common.utils.file.FileUtils;
import com.basic.common.utils.uuid.ULID;
import com.basic.minio.cient.CustomMinioClient;
import com.basic.minio.config.MinioConfig;
import com.basic.minio.domain.MinioPearVo;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * minio工具类
 *
 * @author zhaoxy_jn@163.com
 */
@Component
public class MinioUtil {

    private CustomMinioClient customMinioClient;

    @Resource
    private MinioConfig minioConfigInfo;

    // spring自动注入会失败
    @PostConstruct
    public void init() {
        MinioAsyncClient minioClient = MinioAsyncClient.builder()
                .endpoint(minioConfigInfo.getEndpoint())
                .credentials(minioConfigInfo.getAccessKey(), minioConfigInfo.getSecretKey())
                .build();
        customMinioClient = new CustomMinioClient(minioClient);
    }

    /**
     * 获取文件内容和元信息，该文件不存在会抛异常
     * @param object object
     * @return StatObjectResponse
     */
    public StatObjectResponse statObject(String object) {
        try {
            return customMinioClient.statObject(StatObjectArgs.builder()
                            .bucket(minioConfigInfo.getBucketName())
                            .object(object)
                            .build())
                            .get();
        } catch (Exception e) {
            throw new RuntimeException("获取文件信息失败", e);
        }
    }

    /**
     * 从MinIO服务器获取对象信息
     * 该方法使用了SneakyThrows注解来处理可能抛出的异常，避免了显式的异常处理代码
     *
     * @param object 对象的名称，即在MinIO桶中的唯一标识符
     * @param offset 读取对象内容的起始偏移量，用于指定从对象的哪个字节开始读取
     * @param contentLength 要读取的对象内容的长度，单位为字节
     * @return 返回GetObjectResponse对象，包含了对象的内容和其他元数据
     */
    public GetObjectResponse getObject(String object, Long offset, Long contentLength) {
        try {
            // 构建获取对象的请求参数
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(minioConfigInfo.getBucketName()) // 设置桶名称，从配置中获取
                    .object(object) // 设置对象名称
                    .offset(offset) // 设置读取的起始偏移量
                    .length(contentLength) // 设置要读取的内容长度
                    .build();

            // 调用MinIO客户端的获取对象方法，并同步等待结果
            return customMinioClient.getObject(getObjectArgs).get();
        } catch (Exception e) {
            throw new RuntimeException("获取文件内容失败", e);
        }
    }

    /**
     * 删除：从MinIO服务器上的指定存储桶中删除一个文件
     *
     * @param objectName 指定要删除的文件对象名称（包括路径和文件名）
     */
    public void deleteFile(String objectName) {
        try {
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                    .bucket(minioConfigInfo.getBucketName()) // 设置桶名称，从配置中获取
                    .object(objectName)
                    .build();

            customMinioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            throw new RuntimeException("删除文件失败", e);
        }
    }

    /**
     * 初始化分片上传
     * @param chunkNum 分片数量
     * @param fileName 文件名称
     * @param contentType 文件类型
     */
    public MinioPearVo initMultiPartUpload(Integer chunkNum, String fileName, String filePath, String contentType) {
        MinioPearVo pearVo = new MinioPearVo();
        try {

            if (StringUtils.isNotEmpty(filePath)) {
                deleteFile(filePath);
            }

            // 设置文件路径
            filePath = getFilePathByFileType(fileName);
            pearVo.setFilePath(filePath);
            //设置分片文件类型
            Multimap<String, String> headerMap = HashMultimap.create();
            headerMap.put("Content-Type", StringUtils.isEmpty(contentType) ? "application/octet-stream" : contentType);
            CreateMultipartUploadResponse uploadResponse = customMinioClient.initMultiPartUpload(minioConfigInfo.getBucketName(), null, filePath,
                    headerMap, null);
            Map<String, String> reqParams = new HashMap<>(2);
            reqParams.put("uploadId", uploadResponse.result().uploadId());

            pearVo.setUploadId(uploadResponse.result().uploadId());
            List<MinioPearVo.PearUploadData> uploads = new ArrayList<>();
            for (int i = 1; i <= chunkNum; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String objectUrl = customMinioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(minioConfigInfo.getBucketName())
                                .object(filePath)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                MinioPearVo.PearUploadData uploadData = new MinioPearVo.PearUploadData();
                uploadData.setUploadUrl(objectUrl).setParkNum(i);
                uploads.add(uploadData);
            }
            pearVo.setParts(uploads);

        }catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("文件上传失败");
        }
        return pearVo;
    }

    /**
     * 合并分片
     * @param chunkNum  分片数量
     * @param contentType 文件类型
     * @param uploadId 上传ID
     */
    public Boolean mergeMultipartUpload(Integer chunkNum, String contentType,String uploadId, String filePath) throws Exception {

        Multimap<String, String> headerMap = HashMultimap.create();
        headerMap.put("Content-Type", StringUtils.isEmpty(contentType) ? "application/octet-stream" : contentType);
        ListPartsResponse listMultipart = customMinioClient.listMultipart(minioConfigInfo.getBucketName(), null, filePath, chunkNum,
                0, uploadId, headerMap, null);
        if (Objects.nonNull(listMultipart)){

            // 创建可修改的分片列表
            List<Part> originalParts = listMultipart.result().partList();
            List<Part> sortedParts = new ArrayList<>(originalParts);

            // 按分片号排序
            sortedParts.sort(Comparator.comparingInt(Part::partNumber));

            // 准备合并参数
            Part[] partsArray = sortedParts.stream()
                    .map(part -> new Part(part.partNumber(), part.etag()))
                    .toArray(Part[]::new);
            customMinioClient.mergeMultipartUpload(minioConfigInfo.getBucketName(), null, filePath,
                    uploadId, partsArray, headerMap, null);
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 根据文件类型获取文件存储路径
     * @param fileName 文件名
     * @return 文件存储路径
     */
    public String getFilePathByFileType(String fileName) {
        // 获取文件扩展名
        String fileExtension = FileUtils.getFileSuffix(fileName).toLowerCase();

        // 根据文件扩展名返回不同的存储路径
        switch (fileExtension) {
            case "psd":
            case "tif":
            case "tiff":
            case "cdr":
            case "ai":
            case "psb":
                return "picture_files/" + DateUtils.datePath() + "/source/" + ULID.getId() + "." + fileExtension;
            case "jpg":
                return "picture_files/" + DateUtils.datePath() + "/jpg/" + ULID.getId() + "." + fileExtension;
            case "png":
                return "picture_files/" + DateUtils.datePath() + "/png/" + ULID.getId() + "." + fileExtension;
            default:
                // 默认情况下，可以抛出异常或返回一个默认路径
                throw new IllegalArgumentException("Unsupported file type: " + fileExtension);
        }
    }
}
